/*
 * 数据管理模块
 *
 * 对备份文件的信息进行存储和管理
 *
 **/

#pragma once

#include <string>
#include <vector>
#include <memory>
#include <shared_mutex>
#include <unordered_map>
#include <signal.h>
#include <easylog.h>
#include "util.hh"
#include "config.hh"
#include "info.hh"
#include "persist.hh"

namespace cloud
{
class data_manager
{
public:
    static data_manager& instance()
    {
        static data_manager mgr;
        return mgr;
    }

    backup_info* update(const backup_info& info)
    {
        // use the reader-writer lock supported by c++14
        // to protect thread safety.
        std::shared_lock<std::shared_mutex> lock(_mtx);

        return &(_fpath2info[info.fpath] = info);
    }

    backup_info* insert(const backup_info& info)
    {
        return update(info);
    }

    backup_info* get_by_fpath(const std::string& fpath)
    {
        std::shared_lock<std::shared_mutex> lock(_mtx);

        if (_fpath2info.find(fpath) == _fpath2info.end())
            return handle_error(fpath);

        return &_fpath2info[fpath];
    }

    backup_info* get_by_url(const std::string& url)
    {
        return get_by_fpath(util::path::dlurl2fpath(url));
    }

    backup_info* get_by_bpath(const std::string& bpath)
    {
        return get_by_fpath(util::path::bpath2fpath(bpath));
    }

    backup_info* get_by_ppath(const std::string& ppath)
    {
        return get_by_fpath(util::path::ppath2fpath(ppath));
    }

    std::vector<backup_info> get_all()
    {
        std::shared_lock<std::shared_mutex> lock(_mtx);

        std::vector<backup_info> v;

        for (auto& kv : _fpath2info)
            v.push_back(kv.second);

        return v;
    }

    void delete_by_fpath(const std::string& fpath)
    {
        std::shared_lock<std::shared_mutex> lock(_mtx);
        _fpath2info.erase(fpath);
    }

private:
    // If can't find the file reconginzed by fpath, 
    // then call this function to check
    backup_info* handle_error(const std::string& fpath)
    {
        std::string bpath = util::path::fpath2bpath(fpath);
        std::string ppath = util::path::fpath2ppath(fpath);

        if (util::path::exists(bpath) || util::path::exists(ppath))
        {
            return insert(backup_info(fpath));
        }
        else
        {
            easylog::info("the file is not exists, check {}", fpath);
            return nullptr;
        }
    }

    data_manager()
    {
        // Initialize the persister,
        // persistence support two directions, files or databases.
        if (config::instance().persist_type() == "file")
            _persister = persist_factory::create<file_persist>();
        else if (config::instance().persist_type() == "sql")
            _persister = persist_factory::create<sql_persist>();
        else
            throw except("data_manager:: unknown persister");

        // When the signal is delivered,
        // first persist the information to disk and then kill the program.
        for (int i = 0; i < 32; ++i)
            signal(i, signal_handler);

        // When the module starts,
        // first loads persistent information into the program.
        load();

        easylog::info("data manager created");
    }

    // Perform persistence when module is destructed.
    ~data_manager() { store(); }

    data_manager(const data_manager&) = delete;
    data_manager& operator=(const data_manager&) = delete;

private:
    void load()
    {
        std::vector<backup_info> infos = _persister->load();
        for (auto& info : infos)
            update(info);

        easylog::debug("load backup infos succ");
    }

    void store()
    {
        _persister->store(get_all());
    }

private:
    static void signal_handler(int signo)
    {
        easylog::info("received signal {}, store now", signo);
        data_manager::instance().store();
        util::killer::kill_self();
    }

private:
    std::unordered_map<std::string, backup_info> _fpath2info;
    std::shared_mutex _mtx; // rw lock
    persist::ptr _persister;
};

}
